Fork me on GitHub

JS之深浅拷贝

浅拷贝

对象

  • Object.assign

    1
    2
    3
    4
    5
    6
    let a = {
    age: 1
    }
    let b = Object.assign({}, a)
    a.age = 2
    console.log(b.age) // 1
  • 展开运算符(…)

1
2
3
4
5
6
let a = {
age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1

但是

1
2
3
4
5
6
7
8
9
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = Object.assign({}, a)
a.jobs.first = 'native'
console.log(b.jobs.first) // native

同样

1
2
3
4
5
6
7
8
9
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = {...a}
a.jobs.first = 'native'
console.log(b.jobs.first) // native

浅拷贝只解决了第一层的问题,如果接下去的值中还有对象的话,那么就又回到刚开始的话题了,两者享有相同的引用。要解决这个问题,我们需要引入深拷贝。

数组

我们可以利用数组的一些方法比如:slice、concat 返回一个新数组的特性来实现拷贝。

1
2
3
4
5
6
7
8
var arr = ['old', 1, true, null, undefined];
var new_arr = arr.concat();
new_arr[0] = 'new';
console.log(arr) // ["old", 1, true, null, undefined]
console.log(new_arr) // ["new", 1, true, null, undefined]

但是

1
2
3
4
5
6
7
8
9
var arr = [{old: 'old'}, ['old']];
var new_arr = arr.concat();
arr[0].old = 'new';
arr[1][0] = 'new';
console.log(arr) // [{old: 'new'}, ['new']]
console.log(new_arr) // [{old: 'new'}, ['new']]

同样,这两种方法也只能拷贝一层,克隆的并不彻底。

如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。

我们把这种复制引用的拷贝方法称之为浅拷贝,与之对应的就是深拷贝,深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。

浅拷贝的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
var shallowCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}

深拷贝

使用JSON.parse(JSON.stringify(object))

1
2
3
4
5
6
7
8
9
let a = {
age: 1,
jobs: {
first: 'FE'
}
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

但是该方法也是有局限性的:

  • 忽略 undefined
  • 忽略 symbol
  • 不能序列化函数
  • 不能解决循环引用的对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let obj = {
a: 1,
b: {
c: 2,
d: 3,
},
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
// 因为JSON.stringify不能解决循环引用的问题,所以这里报错
1
2
3
4
5
6
7
8
9
let a = {
age: undefined,
sex: Symbol('male'),
jobs: function() {},
name: 'yck'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "yck"}
// 忽略了 symbol、undefined、函数

递归实现深拷贝

在拷贝的时候判断一下属性值的类型,如果是对象,就递归调用深拷贝函数.

1
2
3
4
5
6
7
8
9
10
var deepCopy = function(obj) {
if (typeof obj !== 'object') return;
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
undefined